我們今天要來接續昨天實作的帳務報表頁面,進行進一步優化,主要是優化分類支出的呈現,讓使用者能快速掌握消費狀況。在現有的圓餅圖基礎上,新增顯示每個分類支出比例的區塊,並且對頁面下方的物品清單進行改進,讓使用者可以看到每個分類的總金額。
因為要實作分類比例區塊和優化物品清單,所以需要來更新 ViewModel。先宣告兩個 @Published
的變數:categoryPercentages 和 categoryTotals。
@Published var categoryPercentages: [(category: ItemCategory, percentage: Double)] = []
@Published var categoryTotals: [(category: ItemCategory, total: Double)] = []
接下來讓這兩個變數擁有資料:
func calculateCategoryPercentages(items: [Item]) {
let totalAmount = items.reduce(0) { $0 + $1.price }
if totalAmount > 0 {
var categoryAmounts: [ItemCategory: Double] = [:]
// 計算每個分類的總金額
for item in items {
let category = item.category
categoryAmounts[category, default: 0] += item.price
}
// 轉換為 (分類, 總金額) 的形式
categoryTotals = categoryAmounts.map { (category, amount) in
(category, amount)
}.sorted { $0.1 > $1.1 }
// 轉換為 (分類, 百分比) 的形式
categoryPercentages = categoryAmounts.map { (category, amount) in
(category, (amount / totalAmount) * 100)
}.sorted { $0.1 > $1.1 } // 依百分比排序
}
}
別忘了在取得資料後就呼叫它:在 fetchData()
函數中呼叫 calculateCategoryPercentages。
func fetchData() {
...略
calculateCategoryPercentages(items: items)
}
有了資料來源,就可以實作比例區塊了!建立 CategoryPercentageView 並繼承 View。
struct CategoryPercentageView: View {
var categoriesWithPercentages: [(ItemCategory, Double)]
@State private var currentPage = 0
var body: some View {
}
}
CategoryPercentageView 顯示每個分類的百分比,一次最顯示 6 個分類,超過 6 個分類時,可以左右滑動來查看其餘分類,下方有點點來提示目前頁數。所以我們要使用 TabView
來完成可以左右滑動的分頁效果。
TabView
是 SwiftUI 中用來顯示多個畫面的一個容器,支持用戶在不同的畫面之間通過手勢滑動或點擊切換。常見的使用場景包括在 App 中實現底部的選單欄或頁面之間的切換。TabView
可以透過 PageTabViewStyle
來啟用分頁效果,讓使用者能夠水平滑動不同頁面。此外,它還可以使用自定義的頁面指示器來顯示目前頁數,提供靈活的視覺效果和互動體驗。
參考資料:
了解 TabView
的用法後,先把 TabView
加入到程式碼中:
VStack {
TabView(selection: $currentPage) {
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) // 隱藏內建的頁面指示器
.frame(height: 160) // 固定高度,支持滑動
.overlay(
PageControl(currentPage: $currentPage, numberOfPages: (categoriesWithPercentages.count + 5) / 6)
.padding(.top, 180) // 放置自訂頁面指示器,不覆蓋分類
)
.onChange(of: currentPage) { _ in
print("Current Page: \(currentPage)")
}
}
.frame(height: 200) // 確保整體高度足夠顯示3行內容
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 1)
)
struct PageControl: View {
@Binding var currentPage: Int
var numberOfPages: Int
var body: some View {
HStack(spacing: 8) {
ForEach(0..<numberOfPages, id: \.self) { page in
Circle()
.fill(page == currentPage ? Color.black : Color.gray)
.frame(width: 8, height: 8)
}
}
}
}
這時候就可以把我們要顯示的分類比例加進來:
ForEach(0..<(categoriesWithPercentages.count + 5) / 6) { pageIndex in
LazyVGrid(columns: columns, spacing: 12) {
ForEach(0..<6, id: \.self) { index in
if pageIndex * 6 + index < categoriesWithPercentages.count {
let (category, percentage) = categoriesWithPercentages[pageIndex * 6 + index]
HStack {
Circle()
.fill(Color(hexString: category.categoryGroup.colorHex) ?? .black)
.frame(width: 20, height: 20)
Text(category.name)
.font(.body)
.lineLimit(1)
Text(String(format: "%.1f%%", percentage))
.font(.body)
}
.padding(2)
} else {
HStack {
Circle()
.fill(.clear)
.frame(width: 20, height: 20)
Text("")
.font(.caption)
.lineLimit(1)
Text("")
.font(.caption)
}
.padding(2)
}
}
}
.padding()
.tag(pageIndex)
}
這樣 CategoryPercentageView 的部分就完成了~
可以將剛剛完成的 CategoryPercentageView 加入到 ReportView 之中:
CategoryPercentageView(categoriesWithPercentages: viewModel.categoryPercentages)
.padding()
接著呢,我們來優化下方的列表。現況是顯示物品的列表,我們來把它改成顯示分類的總金額。
List {
ForEach(viewModel.categoryTotals, id: \.category.id) { category, total in
HStack {
Image(systemName: category.iconName)
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(Color(hexString: category.categoryGroup.colorHex))
Text(category.name)
.font(.headline)
Spacer()
Text(String(format: "%.0f", total))
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
.listStyle(InsetGroupedListStyle())
別忘了把剛剛寫完的 ReportView 與側邊欄整合起來唷!
NavigationLink(destination: ReportView()) {
MenuButton(title: "帳務報表", icon: "chart.pie")
}
.padding(8)
這次我們優化帳務報表頁面,不僅在圓餅圖下方新增了分類比例,還優化了物品清單,讓使用者能更直覺地了解每個分類的總金額。明天我們將繼續優化帳務報表頁面,今天就先寫到這邊,我們明天見!